package com.liaoyifan.core.valid;

import com.liaoyifan.core.function.Matching;
import java.io.Serial;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@SuppressWarnings("unused")
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Lang {

    public static boolean isNotBlank(String str) {
        return !isBlank(str);
    }

    public static boolean isBlank(String str) {
        return str == null || str.isBlank();
    }

    public static boolean isNotBlank(CharSequence str) {
        return !isBlank(str);
    }

    public static boolean isBlank(CharSequence str) {
        if (str != null) {
            int strLen = str.length();
            if (strLen > 0) {
                for (int i = 0; i < strLen; ++i) {
                    if (!Character.isWhitespace(str.charAt(i))) {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    public static boolean isEmpty(Map<?, ?> map) {
        return map == null || map.isEmpty();
    }

    public static boolean isEmpty(Object[] array) {
        return array == null || array.length == 0;
    }

    public static boolean isEmpty(Collection<?> collection) {
        return collection == null || collection.isEmpty();
    }

    public static boolean isEmpty(Object obj) {
        if (obj == null) {
            return true;
        } else if (obj instanceof CharSequence) {
            return ((CharSequence) obj).isEmpty();
        } else if (obj.getClass().isArray()) {
            return Array.getLength(obj) == 0;
        } else if (obj instanceof Collection) {
            return ((Collection<?>) obj).isEmpty();
        } else if (obj instanceof Map) {
            return ((Map<?, ?>) obj).isEmpty();
        } else if (obj instanceof Optional) {
            return ((Optional<?>) obj).isEmpty();
        } else {
            return false;
        }
    }

    public static boolean isNotEmpty(Map<?, ?> map) {
        return !isEmpty(map);
    }

    public static boolean isNotEmpty(Object[] array) {
        return !isEmpty(array);
    }

    public static boolean isNotEmpty(Collection<?> collection) {
        return !isEmpty(collection);
    }

    public static boolean isNotEmpty(Object obj) {
        return !isEmpty(obj);
    }

    public static boolean startsWithIgnoreCase(String str, String prefix) {
        return str != null
                && prefix != null
                && str.length() >= prefix.length()
                && str.regionMatches(true, 0, prefix, 0, prefix.length());
    }

    public static boolean endsWithIgnoreCase(String str, String suffix) {
        return str != null
                && suffix != null
                && str.length() >= suffix.length()
                && str.regionMatches(
                        true, str.length() - suffix.length(), suffix, 0, suffix.length());
    }

    public static String[] split(String src, String delimiter) {
        int maxParts = src.length() / delimiter.length() + 2;
        int[] positions = new int[maxParts];
        int delLength = delimiter.length();
        int j = 0;
        int count = 0;

        int i;
        for (positions[0] = -delLength; (i = src.indexOf(delimiter, j)) != -1; j = i + delLength) {
            ++count;
            positions[count] = i;
        }

        ++count;
        positions[count] = src.length();
        String[] result = new String[count];

        for (int var9 = 0; var9 < count; ++var9) {
            result[var9] = src.substring(positions[var9] + delLength, positions[var9 + 1]);
        }

        return result;
    }

    public static <T extends Comparable<T>> int compareTo(T value, T requirement) {
        Assert.notNull(value, "value cannot be null.");
        Assert.notNull(requirement, "requirement cannot be null.");
        return value.compareTo(requirement);
    }

    public static <T extends Comparable<T>> boolean eq(T value, T requirement) {
        return compareTo(value, requirement) == 0;
    }

    public static <T extends Comparable<T>> boolean gt(T value, T requirement) {
        return compareTo(value, requirement) > 0;
    }

    public static <T extends Comparable<T>> boolean lt(T value, T requirement) {
        return compareTo(value, requirement) < 0;
    }

    public static <T extends Comparable<T>> boolean gte(T value, T requirement) {
        return compareTo(value, requirement) >= 0;
    }

    public static <T extends Comparable<T>> boolean lte(T value, T requirement) {
        return compareTo(value, requirement) <= 0;
    }

    public static boolean isNumeric(CharSequence str) {
        return isAllCharMatch(str, Character::isDigit);
    }

    public static boolean isAllCharMatch(CharSequence value, Matching<Character> matcher) {
        if (isNotBlank(value)) {
            int i = value.length();
            do {
                --i;
                if (i < 0) {
                    return true;
                }
            } while (matcher.match(value.charAt(i)));
        }
        return false;
    }

    public static boolean equals(CharSequence str1, CharSequence str2) {
        return equals(str1, str2, false);
    }

    public static boolean equalsIgnoreCase(CharSequence str1, CharSequence str2) {
        return equals(str1, str2, true);
    }

    public static boolean equals(CharSequence str1, CharSequence str2, boolean ignoreCase) {
        if (null == str1) {
            return str2 == null;
        } else if (null == str2) {
            return false;
        } else {
            return ignoreCase
                    ? str1.toString().equalsIgnoreCase(str2.toString())
                    : str1.toString().contentEquals(str2);
        }
    }

    public static String format(CharSequence template, Object... params) {
        if (null == template) {
            return "null";
        } else {
            return isNotEmpty(params) && isNotBlank(template)
                    ? StrFormatter.format(template.toString(), params)
                    : template.toString();
        }
    }

    public static String replaceIgnoreCase(
            CharSequence str, CharSequence searchStr, CharSequence replacement) {
        return StrFormatter.replace(str, 0, searchStr, replacement, true);
    }

    public static String replace(
            CharSequence str, CharSequence searchStr, CharSequence replacement) {
        return StrFormatter.replace(str, 0, searchStr, replacement, false);
    }

    public static String join(Iterable<?> iterable, String separator) {
        if (iterable == null) {
            return null;
        }
        Iterator<?> iterator = iterable.iterator();
        if (!iterator.hasNext()) {
            return "";
        }
        Object first = iterator.next();
        if (!iterator.hasNext()) {
            return first == null ? "" : first.toString();
        }

        // two or more elements
        StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small
        if (first != null) {
            buf.append(first);
        }

        while (iterator.hasNext()) {
            if (separator != null) {
                buf.append(separator);
            }
            Object obj = iterator.next();
            if (obj != null) {
                buf.append(obj);
            }
        }
        return buf.toString();
    }

    @SuppressWarnings("ClassCanBeRecord")
    private static class StrFinder implements Serializable {

        @Serial private static final long serialVersionUID = 1L;

        private final CharSequence text;
        private final CharSequence strToFind;
        private final boolean caseInsensitive;

        public StrFinder(CharSequence strToFind, CharSequence text, boolean caseInsensitive) {
            Assert.notBlank(strToFind, "strToFind cannot be blank");
            Assert.notBlank(text, "text cannot be blank");
            this.strToFind = strToFind;
            this.text = text;
            this.caseInsensitive = caseInsensitive;
        }

        public int start(int from) {
            int subLen = this.strToFind.length();
            if (from < 0) {
                from = 0;
            }

            int endLimit = this.text.length();
            endLimit = endLimit - subLen + 1;

            for (int i = from; i < endLimit; ++i) {
                if (isSubEquals(this.text, i, this.strToFind, 0, subLen, this.caseInsensitive)) {
                    return i;
                }
            }

            return -1;
        }

        public static boolean isSubEquals(
                CharSequence str1,
                int start1,
                CharSequence str2,
                int start2,
                int length,
                boolean ignoreCase) {
            return null != str1
                    && null != str2
                    && str1.toString()
                            .regionMatches(ignoreCase, start1, str2.toString(), start2, length);
        }
    }

    private static class StrFormatter {
        public static String format(String strPattern, Object... argArray) {
            return formatWith(strPattern, "{}", argArray);
        }

        public static String formatWith(String strPattern, String placeHolder, Object... argArray) {
            if (Lang.isNotBlank(strPattern)
                    && Lang.isNotBlank(placeHolder)
                    && Lang.isNotEmpty(argArray)) {
                int strPatternLength = strPattern.length();
                int placeHolderLength = placeHolder.length();
                StringBuilder buff = new StringBuilder(strPatternLength + 50);
                int handledPosition = 0;

                for (int argIndex = 0; argIndex < argArray.length; ++argIndex) {
                    int deliIndex = strPattern.indexOf(placeHolder, handledPosition);
                    if (deliIndex == -1) {
                        if (handledPosition == 0) {
                            return strPattern;
                        }

                        buff.append(strPattern, handledPosition, strPatternLength);
                        return buff.toString();
                    }

                    if (deliIndex > 0 && strPattern.charAt(deliIndex - 1) == '\\') {
                        if (deliIndex > 1 && strPattern.charAt(deliIndex - 2) == '\\') {
                            buff.append(strPattern, handledPosition, deliIndex - 1);
                            buff.append(str(argArray[argIndex], StandardCharsets.UTF_8));
                            handledPosition = deliIndex + placeHolderLength;
                        } else {
                            --argIndex;
                            buff.append(strPattern, handledPosition, deliIndex - 1);
                            buff.append(placeHolder.charAt(0));
                            handledPosition = deliIndex + 1;
                        }
                    } else {
                        buff.append(strPattern, handledPosition, deliIndex);
                        buff.append(str(argArray[argIndex], StandardCharsets.UTF_8));
                        handledPosition = deliIndex + placeHolderLength;
                    }
                }

                buff.append(strPattern, handledPosition, strPatternLength);
                return buff.toString();
            } else {
                return strPattern;
            }
        }

        public static String str(byte[] data, Charset charset) {
            if (data == null) {
                return null;
            } else {
                return null == charset ? new String(data) : new String(data, charset);
            }
        }

        public static String str(Byte[] data, Charset charset) {
            if (data == null) {
                return null;
            } else {
                byte[] bytes = new byte[data.length];

                for (int i = 0; i < data.length; ++i) {
                    Byte dataByte = data[i];
                    bytes[i] = null == dataByte ? -1 : dataByte;
                }

                return str(bytes, charset);
            }
        }

        public static String str(ByteBuffer data, Charset charset) {
            if (null == charset) {
                charset = Charset.defaultCharset();
            }

            return charset.decode(data).toString();
        }

        public static String toString(Object obj) {
            if (null == obj) {
                return null;
            } else if (obj instanceof long[]) {
                return Arrays.toString((long[]) obj);
            } else if (obj instanceof int[]) {
                return Arrays.toString((int[]) obj);
            } else if (obj instanceof short[]) {
                return Arrays.toString((short[]) obj);
            } else if (obj instanceof char[]) {
                return Arrays.toString((char[]) obj);
            } else if (obj instanceof byte[]) {
                return Arrays.toString((byte[]) obj);
            } else if (obj instanceof boolean[]) {
                return Arrays.toString((boolean[]) obj);
            } else if (obj instanceof float[]) {
                return Arrays.toString((float[]) obj);
            } else if (obj instanceof double[]) {
                return Arrays.toString((double[]) obj);
            } else {
                if (obj.getClass().isArray()) {
                    try {
                        return Arrays.deepToString((Object[]) obj);
                    } catch (Exception ignored) {
                    }
                }
                return obj.toString();
            }
        }

        public static String str(Object obj, Charset charset) {
            if (null == obj) {
                return null;
            } else if (obj instanceof String) {
                return (String) obj;
            } else if (obj instanceof byte[]) {
                return str((byte[]) obj, charset);
            } else if (obj instanceof Byte[]) {
                return str((Byte[]) obj, charset);
            } else if (obj instanceof ByteBuffer) {
                return str((ByteBuffer) obj, charset);
            } else {
                return obj.getClass().isArray() ? toString(obj) : obj.toString();
            }
        }

        public static String str(CharSequence cs) {
            return null == cs ? null : cs.toString();
        }

        public static int indexOf(
                CharSequence text, CharSequence searchStr, int from, boolean ignoreCase) {
            if (Lang.isNotBlank(text) && Lang.isNotBlank(searchStr)) {
                return new StrFinder(searchStr, text, ignoreCase).start(from);
            } else {
                return Lang.equals(text, searchStr) ? 0 : -1;
            }
        }

        public static String replace(
                CharSequence str,
                int fromIndex,
                CharSequence searchStr,
                CharSequence replacement,
                boolean ignoreCase) {
            if (Lang.isNotBlank(str) && Lang.isNotBlank(searchStr)) {
                if (null == replacement) {
                    replacement = "";
                }

                int strLength = str.length();
                int searchStrLength = searchStr.length();
                if (strLength < searchStrLength) {
                    return str(str);
                } else if (fromIndex > strLength) {
                    return str(str);
                } else {
                    if (fromIndex < 0) {
                        fromIndex = 0;
                    }

                    StringBuilder result =
                            new StringBuilder(strLength - searchStrLength + replacement.length());
                    if (0 != fromIndex) {
                        result.append(str.subSequence(0, fromIndex));
                    }

                    int preIndex;
                    int index;
                    for (preIndex = fromIndex;
                            (index = indexOf(str, searchStr, preIndex, ignoreCase)) > -1;
                            preIndex = index + searchStrLength) {
                        result.append(str.subSequence(preIndex, index));
                        result.append(replacement);
                    }

                    if (preIndex < strLength) {
                        result.append(str.subSequence(preIndex, strLength));
                    }

                    return result.toString();
                }
            } else {
                return str(str);
            }
        }
    }
}
